package com.wyl.niupi.framework.util.jdkExt;

import java.math.BigDecimal;

/**
 * BigDecimal计算器，用于加减乘除计算，避免精度丢失的问题。 <br/>
 * <b>注意：<br/>
 * 如果不进行setScale()，getValue默认的精度为4位<br/>
 * 如果不进行setMode()，getValue默认是四舍五入<br/>
 * </b><br/>
 *
 * <code>
 *     使用示例：<br/>
 *     _BigDecimalUtil bu = _BigDecimalUtil.getInstance(); <br/>
 *     Double result = bu.setValue(2).add(3).div(3).sub(3).mul(1.1).getValue(); <br/>
 *     System.out.println(result); //-1.4667 <br/>
 * </code>
 * @author 吴宇亮 on 2019/10/30 0030 上午 10:08
 */
public class _BigDecimalUtil {

    private _BigDecimalUtil(){

    }

    public static _BigDecimalUtil getInstance(){
        return new _BigDecimalUtil();
    }

    /**
     * 特殊值，0
     */
    private static final Double zero = 0D;

    /**
     * 除法默认保留divideScale位小数，避免出现无限循环小数时抛出异常
     */
    private static final int divideScale = 10;

    /**
     * 除法默认将结果四舍五入，避免出现无限循环小数时抛出异常
     */
    private static final int divideMode = BigDecimal.ROUND_HALF_EVEN;

//    enum roundingMode{
//        /**
//         * 取右边最近的整数
//         */
//        ROUND_CEILING
//        /**
//         * 去掉小数部分取整，也就是正数取左边，负数取右边，相当于向原点靠近的方向取整
//         */
//        ,ROUND_DOWN
//        /**
//         * 取左边最近的正数
//         */
//        ,ROUND_FLOOR
//        /**
//         * 五舍六入，负数先取绝对值再五舍六入再负数
//         */
//        ,ROUND_HALF_DOWN
//        /**
//         * 四舍五入，负数原理同上
//         */
//        ,ROUND_HALF_UP
//        /**
//         * 整数位若是奇数则四舍五入，若是偶数则五舍六入
//         */
//        ,ROUND_HALF_EVEN
//    }

    /**
     * 默认getValue是四舍五入
     */
    private int mode = BigDecimal.ROUND_HALF_UP;

    /**
     * 默认getValue保留四位小数
     */
    private int scale = 4;

    /**
     * 计算结果
     */
    private BigDecimal value;

    public Double getValue() {
        return value.setScale(scale, mode).doubleValue();
    }

    public _BigDecimalUtil setValue(Integer value) {
        this.value = BigDecimal.valueOf(value);
        return this;
    }

    public _BigDecimalUtil setValue(Double value) {
        this.value = BigDecimal.valueOf(value);
        return this;
    }

    public _BigDecimalUtil add(Integer anotherValue){
        calculateCheck();
        value = value.add(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil add(Double anotherValue){
        calculateCheck();
        value = value.add(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil sub(Integer anotherValue){
        calculateCheck();
        value = value.subtract(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil sub(Double anotherValue){
        calculateCheck();
        value = value.subtract(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil mul(Integer anotherValue){
        calculateCheck();
        value = value.multiply(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil mul(Double anotherValue){
        calculateCheck();
        value = value.multiply(BigDecimal.valueOf(anotherValue));
        return this;
    }

    public _BigDecimalUtil div(Integer anotherValue){
        calculateCheck();
        divideCheck();
        value = value.divide(BigDecimal.valueOf(anotherValue), divideScale, divideMode);
        return this;
    }

    public _BigDecimalUtil div(Double anotherValue){
        calculateCheck();
        divideCheck();
        value = value.divide(BigDecimal.valueOf(anotherValue), divideScale, divideMode);
        return this;
    }

    private void calculateCheck(){
        if(value == null){
            throw new NullPointerException("_BigDecimalCalculate的初始值为null，请先调用setValue()在进行计算");
        }
    }

    private void divideCheck(){
        if(zero == value.doubleValue()){
            throw new IllegalArgumentException("被除数不能为0");
        }
    }

    /**
     * 设置最后计算结果是采用四舍五入，还是其他方法。如不设置，则默认为"四舍五入"
     * @param mode 参考BigDecimal.ROUND_HALF_EVEN
     */
    public _BigDecimalUtil setMode(int mode) {
        this.mode = mode;
        return this;
    }

    /**
     * 设置最后计算结果的精度。如不设置，则默认为"保留4位小数"
     * @param scale 保留小数位数
     */
    public _BigDecimalUtil setScale(int scale) {
        if(scale < 0){
            throw new IllegalArgumentException("精度不能小于0");
        }
        this.scale = scale;
        return this;
    }
}
